﻿#region Using Statements
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using System;
using System.Collections.Generic;

using Xeno_Race.Helpers;
using Xeno_Race.Graphics;
#endregion

namespace Xeno_Race.Game
{
    public class Track
    {
        /// <summary>
        /// Setter/Getter for collision track
        /// </summary>
        /// <returns>CollisionTrack</returns>
        public CollisionTrack CollisionTrack
        {
            get { return collisionTrack; }
            set { collisionTrack = value; }
        }

        /// <summary>
        /// The collision track this instance will use
        /// </summary>
        private CollisionTrack collisionTrack;

        /// <summary>
        /// Setter/Getter for ground model
        /// </summary>
        /// <returns>Model</returns>
        public Model GroundModel
        {
            get { return groundModel; }
            set { groundModel = value; }
        }

        /// <summary>
        /// The ground model this instance will use
        /// </summary>
        private Model groundModel;

        /// <summary>
        /// Setter/Getter for wall texture
        /// </summary>
        /// <returns>Texture2D</returns>
        public Texture2D WallTexture
        {
            get { return wallTexture; }
            set { wallTexture = value; }
        }

        /// <summary>
        /// The wall texture this instance will use
        /// </summary>
        private Texture2D wallTexture;

        /// <summary>
        /// Textured vertices to draw the trackwalls
        /// </summary>
        private VertexPositionNormalTexture[] rightWall, leftWall;

        #region Initialization
        /// <summary>
        /// Constructor
        /// </summary>
        public Track(CollisionTrack currCollisionTrack)
        {
            this.collisionTrack = currCollisionTrack;
            rightWall = generateWallVertices(true);
            leftWall = generateWallVertices(false);
        }
        #endregion

        /// <summary>
        /// Draw method that only displays regular borders
        /// </summary>
        public void Draw(ChaseCamera camera)
        {
            DrawWalls(camera);
        }

        /// <summary>
        /// Draw method that displays regular borders as well as the otherwise invisble collision track
        /// </summary>
        public void Draw(Ship ship, ChaseCamera camera, Texture2D trackTexture)
        {
            DrawWalls(camera);
            DrawCollisionTrack(ship, camera, trackTexture);
        }

        /// <summary>
        /// Generate regular side wall vertices with tex coords
        /// </summary>
        private VertexPositionNormalTexture[] generateWallVertices(bool right)
        {
            float wallHeight = 0.1f * this.collisionTrack.TrackScaleFactor;
            float wallOffsetX = 1.1f * this.collisionTrack.HalfTrackWidth;
            float wallOffsetY = 0.10f * this.collisionTrack.HalfTrackWidth;
            float texY = 0.5f;

            List<VertexPositionNormalTexture> verticesList = new List<VertexPositionNormalTexture>();
            Vector3 currentNormal;

            for (int i = 1; i <  this.collisionTrack.ExtendedTrackPoints.Count - 1; i++)
            {
                currentNormal = Vector3.Up;
                Vector3 shipDir = this.collisionTrack.ExtendedTrackPoints[i + 1] 
                    - this.collisionTrack.ExtendedTrackPoints[i];
                // shipDir.Normalize();

                Vector3 sideDir = Vector3.Cross(currentNormal, shipDir);
                sideDir.Normalize();

                currentNormal = Vector3.Cross(shipDir, sideDir);
                currentNormal.Normalize();

                Vector3 lowerPoint;
                Vector3 upperPoint;

                if (right)
                {
                    lowerPoint = this.collisionTrack.ExtendedTrackPoints[i]
                        - sideDir * wallOffsetX
                        - currentNormal * wallOffsetY;

                    upperPoint = this.collisionTrack.ExtendedTrackPoints[i]
                        - sideDir * wallOffsetX
                        + currentNormal * wallHeight;
                }
                else
                {
                    lowerPoint = this.collisionTrack.ExtendedTrackPoints[i]
                        + sideDir * wallOffsetX
                        - currentNormal * wallOffsetY;

                    upperPoint = this.collisionTrack.ExtendedTrackPoints[i]
                        + sideDir * wallOffsetX
                        + currentNormal * wallHeight;

                    sideDir = Vector3.Negate(sideDir);
                }

                VertexPositionNormalTexture vertex;
                vertex = new VertexPositionNormalTexture(lowerPoint, sideDir, new Vector2(texY, 1));
                verticesList.Add(vertex);
                vertex = new VertexPositionNormalTexture(upperPoint, sideDir, new Vector2(texY, 0.40f));
                verticesList.Add(vertex);

                if (i % 2 == 0)
                    texY = 0.5f;
                else if (i % 4 == 3)
                    texY = 1.0f;
                else
                    texY = 0;                
            }

            //// Additional vertices to close track
            VertexPositionNormalTexture extraVert = verticesList[0];
            extraVert.TextureCoordinate.X = texY;
            verticesList.Add(extraVert);

            extraVert = verticesList[1];
            extraVert.TextureCoordinate.X = texY;
            verticesList.Add(extraVert);

            return verticesList.ToArray();
        }

        #region Track drawing
        /// <summary>
        /// Static basic effect access, also sets device and vertex declaration!
        /// </summary>
        /// <returns>BasicEffect</returns>
        public static BasicEffect BasicEffect
        {
            get { return basicEffect; }
            set
            {
                // Setup basic render effect for drawing track relatet stuff
                basicEffect = value;
                device = basicEffect.GraphicsDevice;
                trackVertexDeclaration = new VertexDeclaration(device, VertexPositionNormalTexture.VertexElements);

                // Set default world matrix
                basicEffect.World = Matrix.Identity;

                // Enable using textures
                basicEffect.TextureEnabled = true;

                // Default light
                basicEffect.LightingEnabled = true;
                basicEffect.AmbientLightColor = new Vector3(0.1f, 0.1f, 0.1f);
                basicEffect.PreferPerPixelLighting = true;

                Vector3 lightDirection = new Vector3(6, -10, 2);
                lightDirection.Normalize();
                basicEffect.DirectionalLight0.Direction = lightDirection;
                basicEffect.DirectionalLight0.DiffuseColor = Color.White.ToVector3();
                basicEffect.DirectionalLight0.Enabled = true;
                basicEffect.DirectionalLight1.Enabled = false;
                basicEffect.DirectionalLight2.Enabled = false;

                basicEffect.VertexColorEnabled = false;
            }
        }
        /// <summary>
        /// Static basic effect for drawing simple objects
        /// </summary>
        private static BasicEffect basicEffect;

        /// <summary>
        /// Static device access for the draw methods
        /// </summary>
        private static GraphicsDevice device;

        /// <summary>
        /// Static var that informs the device of which type the vertex data is
        /// </summary>
        private static VertexDeclaration trackVertexDeclaration;

        /// <summary>
        /// Draw the trackwalls
        /// </summary>
        public void DrawWalls(ChaseCamera camera)
        {
            // Set render options
            device.RenderState.CullMode = CullMode.None;

            // Use the matrices provided by the chase camera
            basicEffect.View = camera.View;
            basicEffect.Projection = camera.Projection;

            // Textures
            basicEffect.Texture = wallTexture;

            basicEffect.Begin();
            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                device.VertexDeclaration = trackVertexDeclaration;
                device.DrawUserPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleStrip, rightWall, 0, rightWall.Length - 2);
                device.DrawUserPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleStrip, leftWall, 0, leftWall.Length - 2);
                pass.End();
            }
            basicEffect.End();
        }

        /// <summary>
        /// Draw the (otherwise invisble) collision track for debuggin purposes
        /// </summary>
        public void DrawCollisionTrack(Ship ship, ChaseCamera camera, Texture2D trackTexture)
        {
            // Set render options
            device.RenderState.CullMode = CullMode.None;
            device.RenderState.PointSize = 10;

            // Use the matrices provided by the chase camera
            basicEffect.View = camera.View;
            basicEffect.Projection = camera.Projection;

            // Textures
            basicEffect.Texture = trackTexture;

            // Draws track ground and walls
            drawTrack(this.collisionTrack.TrackVertices);
            device.RenderState.FillMode = FillMode.WireFrame;
            drawTrack(this.collisionTrack.TrackLeftSideVertices);
            drawTrack(this.collisionTrack.TrackRightSideVertices);

            // Draws the track points itself and helper lines
            drawTrackPoints();
            drawLineToShip(ship);

            // Set rendermode back to solid
            device.RenderState.FillMode = FillMode.Solid;
        }

        /// <summary>
        /// Draws ground planes
        /// </summary>
        private void drawTrack(VertexPositionNormalTexture[] trackVertices)
        {
            basicEffect.Begin();
            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                device.VertexDeclaration = trackVertexDeclaration;
                device.DrawUserPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleStrip, trackVertices, 0, trackVertices.Length - 2);
                pass.End();
            }
            basicEffect.End();
        }

        /// <summary>
        /// Draws the interpolated trackpoints
        /// </summary>  
        private void drawTrackPoints()
        {
            basicEffect.Begin();
            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                device.VertexDeclaration = trackVertexDeclaration;

                // Initialize an array of indices of type short.
                int points = this.collisionTrack.ExtendedTrackPoints.Count;
                short[] lineListIndices = new short[(points * 2) - 2];

                // Populate the array with references to indices in the vertex buffer
                for (int i = 0; i < points - 1; i++)
                {
                    lineListIndices[i] = (short)(i);
                }

                VertexPositionColor[] pointList = new VertexPositionColor[points];

                for (int i = 0; i < this.collisionTrack.ExtendedTrackPoints.Count; i++)
                {
                    pointList[i] = new VertexPositionColor(this.collisionTrack.ExtendedTrackPoints[i], Color.White);
                }

                device.DrawUserIndexedPrimitives<VertexPositionColor>(
                    PrimitiveType.PointList,
                    pointList,
                    0,                  // vertex buffer offset to add to each element of the index buffer
                    points,             // number of vertices in pointList
                    lineListIndices,    // the index buffer
                    0,                  // first index element to read
                    points - 1            // number of primitives to draw
                );

                pass.End();
            }
            basicEffect.End();
        }

        /// <summary>
        /// Draws helper lines (ship pos to interpolated trackpoint and the interpolated normal)
        /// </summary>  
        private void drawLineToShip(Ship ship)
        {
            basicEffect.Begin();
            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                device.VertexDeclaration = trackVertexDeclaration;

                // Initialize an array of indices of type short.
                int points = 3;
                short[] lineListIndices = new short[(points * 2) - 2];

                // Populate the array with references to indices in the vertex buffer
                for (int i = 0; i < points - 1; i++)
                {
                    lineListIndices[i * 2] = (short)(i);
                    lineListIndices[(i * 2) + 1] = (short)(i + 1);
                }

                VertexPositionColor[] pointList = new VertexPositionColor[points];

                pointList[0] = new VertexPositionColor(ship.InterpolatedPosition + 0.1f * this.collisionTrack.TrackScaleFactor * ship.InterpolatedNormal, Color.White);
                pointList[1] = new VertexPositionColor(ship.InterpolatedPosition, Color.White);
                pointList[2] = new VertexPositionColor(ship.Position, Color.White);

                device.DrawUserIndexedPrimitives<VertexPositionColor>(
                    PrimitiveType.LineList,
                    pointList,
                    0,                  // vertex buffer offset to add to each element of the index buffer
                    points,             // number of vertices in pointList
                    lineListIndices,    // the index buffer
                    0,                  // first index element to read
                    points - 1            // number of primitives to draw
                );

                pass.End();
            }
            basicEffect.End();
        }
        #endregion
    }
}

